package cn.vehicle.service.remote.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.vehicle.biz.cache.AccessTokenCache;
import cn.vehicle.domains.dto.AccessTokenDto;
import cn.vehicle.domains.dto.BaseResponse;
import cn.vehicle.domains.entity.CommonVehicleEntity;
import cn.vehicle.domains.entity.TransportVehicleEntity;
import cn.vehicle.domains.entity.VehicleCommonLedgerEntity;
import cn.vehicle.domains.entity.VehicleTransportLedgerEntity;
import cn.vehicle.enums.EmissionStandardEnum;
import cn.vehicle.enums.InOutTypeEnum;
import cn.vehicle.exception.VehicleAccessException;
import cn.vehicle.service.config.SystemConfig;
import cn.vehicle.service.config.SystemConfigService;
import cn.vehicle.service.remote.TjRemoteService;
import cn.vehicle.utils.Base64ToImageUtil;
import cn.vehicle.utils.VehicleStringUtil;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.io.File;
import java.io.IOException;

/**
 * 天津接口远程调用服务实现类
 * <p>
 * 负责与天津监管平台的接口交互，包括：
 * <ul>
 *   <li>车辆信息上报（注册登记信息）；</li>
 *   <li>获取 access_token 与公钥；</li>
 *   <li>加密传输数据并处理接口返回结果。</li>
 * </ul>
 * </p>
 *
 * <p>主要功能：</p>
 * <ul>
 *   <li>通过 SM2 加密算法对车辆数据进行加密；</li>
 *   <li>使用 OkHttp 发起 HTTP 请求；</li>
 *   <li>实现接口调用的通用封装（requestHandle 方法）。</li>
 * </ul>
 */
@Service
@Slf4j
public class TjRemoteServiceImpl implements TjRemoteService {

    @Resource
    private AccessTokenCache accessTokenCache;

    @Resource
    private SystemConfigService systemConfigService;
    private AccessTokenDto getAccessToken(String username, String password) throws IOException {
        long requestTime = System.currentTimeMillis();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", username);
        jsonObject.put("password", SmUtil.sm3().digestHex(username + requestTime + password));
        jsonObject.put("requestTimestamp", requestTime);
        jsonObject.put("encrypt_method", 2);


        return requestHandle(jsonObject, AccessTokenDto.class, "/api/v1/token/getAccessToken");
    }

    /**
     * 上报车辆注册登记信息至天津监管平台
     * <p>
     * 将普通车辆与运输车辆的基本信息组装为 JSON 数据，
     * 使用 SM2 公钥加密后通过远程接口上报至监管平台。
     * 同时上传相关行驶证、许可证图片。
     * </p>
     *
     * @param commonVehicleEntity 普通车辆信息实体
     * @param vehicleEntity 运输车辆信息实体
     * @return 平台接口响应对象 {@link BaseResponse}
     * @throws IOException 当加密或网络请求失败时抛出
     */
    @Override
    public BaseResponse putRegisterVehicleInfo(CommonVehicleEntity commonVehicleEntity, TransportVehicleEntity vehicleEntity) throws IOException {
        // 组装车辆基础信息数据（普通车辆 + 运输车辆）
        JSONObject data = new JSONObject();
        // 设置车牌号、颜色、类型、发动机等基础字段
        data.put("plateNo", commonVehicleEntity.getVehicleNumber());
        data.put("plateColor", commonVehicleEntity.getVehiclePlateColor());
        data.put("vehicleType", vehicleEntity.getTransportVehicleType());
        data.put("vin", commonVehicleEntity.getVin());
        data.put("engineNo", vehicleEntity.getEngineNo());
        data.put("registerDate", DateUtil.format(commonVehicleEntity.getExpireTime(), DatePattern.PURE_DATE_PATTERN));
        data.put("vehicleBrand", vehicleEntity.getVehicleBrand());
        data.put("vehicleModel", vehicleEntity.getVehicleModel());
        data.put("fuelType", vehicleEntity.getFuelType());
        data.put("engineType", vehicleEntity.getEngineType());
        data.put("engineManufacturer", vehicleEntity.getEngineManufacturer());
        data.put("emissionStandard", EmissionStandardEnum.valueOf(vehicleEntity.getEmissionStandard()).getCode());
        data.put("connectedStatus", vehicleEntity.getNetworkStatus());
        data.put("useCharacter", vehicleEntity.getUsageNature());
        data.put("owner", commonVehicleEntity.getOwnerName());

        // 创建 SM2 加密对象，使用监管平台提供的公钥进行加密
        SM2 sm2 = SmUtil.sm2(null, getPublicKey());
        // 将车辆信息序列化为 JSON 并加密成十六进制字符串
        String value = sm2.encryptHex(data.toJSONString(), CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey);

        log.info("上传政府车辆信息待加密数据：{}", data);
        // 调用本地缓存或远程接口获取有效的 access_token
        String accessToken = getAccessToken();

        // 组装最终请求体，包括加密后的数据与凭证信息
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("access_token", accessToken);
        jsonObject.put("company_code", systemConfigService.get(SystemConfig.companyCode));
        jsonObject.put("encrypt_method", 2);
        // 转换许可证、行驶证图片为 Base64 编码并压缩处理
        jsonObject.put("envLicenseImg", Base64ToImageUtil.imageToBase64LimitSize(vehicleEntity.getAttachedListUrl()));
        jsonObject.put("drivingLicenseImg", Base64ToImageUtil.imageToBase64LimitSize(vehicleEntity.getDrivingLicenseUrl()));
        jsonObject.put("value", value);

        // 通过通用请求处理方法调用天津平台接口并返回响应结果
        return requestHandle(jsonObject, BaseResponse.class, "/api/v1/vehicleInfo/putRegisterVehicleInfo");
    }

    @Override
    public BaseResponse putVehiclePassRecords(VehicleTransportLedgerEntity vehicleTransportLedgerEntity, InOutTypeEnum passStatus, VehicleCommonLedgerEntity vehicleCommonLedgerEntity, CommonVehicleEntity commonVehicleEntity, TransportVehicleEntity transportVehicleEntity) throws IOException {

        String passTime = passStatus == InOutTypeEnum.IN ?
                DateUtil.format(vehicleTransportLedgerEntity.getEntryTime(), DatePattern.NORM_DATETIME_PATTERN) :
                DateUtil.format(vehicleTransportLedgerEntity.getExitTime(), DatePattern.NORM_DATETIME_PATTERN);


        String vehicleHeadImg;
        String vehicleBodyImg;
        String pressTextVehicleImgUrl;
        String pressTextHeadImgUrl;
        String transportGoods;
        String transportVolume;
        String entranceNo;
        String gateNo;
        String handMove;

        if (passStatus == InOutTypeEnum.IN) {
            transportGoods = vehicleTransportLedgerEntity.getEntryCargoName();
            transportVolume = vehicleTransportLedgerEntity.getEntryCargoUnit();
            pressTextVehicleImgUrl = pressTextImg(vehicleTransportLedgerEntity.getEntryVehicleImgUrl(), vehicleTransportLedgerEntity.getVehicleNumber(), passTime, vehicleCommonLedgerEntity.getEntryExitCode(), vehicleCommonLedgerEntity.getEntryRoadCode());
            pressTextHeadImgUrl = pressTextImg(vehicleTransportLedgerEntity.getEntryHeadImgUrl(), vehicleTransportLedgerEntity.getVehicleNumber(), passTime,  vehicleCommonLedgerEntity.getEntryExitCode(), vehicleCommonLedgerEntity.getEntryRoadCode());
            entranceNo = vehicleCommonLedgerEntity.getEntryExitCode();
            gateNo = vehicleCommonLedgerEntity.getEntryRoadCode();

            if ("SYSTEM".equals(vehicleCommonLedgerEntity.getEntryOperator())) {
                handMove = "0";
            } else {
                handMove = "1";
            }
        } else if (passStatus == InOutTypeEnum.OUT) {
            transportGoods = vehicleTransportLedgerEntity.getExitCargoName();
            transportVolume = vehicleTransportLedgerEntity.getExitCargoUnit();
            pressTextVehicleImgUrl = pressTextImg(vehicleTransportLedgerEntity.getExitVehicleImgUrl(), vehicleTransportLedgerEntity.getVehicleNumber(), passTime, vehicleCommonLedgerEntity.getExitEntryExitCode(), vehicleCommonLedgerEntity.getExitRoadCode());
            pressTextHeadImgUrl = pressTextImg(vehicleTransportLedgerEntity.getExitHeadImgUrl(), vehicleTransportLedgerEntity.getVehicleNumber(), passTime, vehicleCommonLedgerEntity.getExitEntryExitCode(), vehicleCommonLedgerEntity.getExitRoadCode());
            entranceNo = vehicleCommonLedgerEntity.getExitEntryExitCode();
            gateNo = vehicleCommonLedgerEntity.getExitRoadCode();

            if ("SYSTEM".equals(vehicleCommonLedgerEntity.getExitOperator())) {
                handMove = "0";
            } else {
                handMove = "1";
            }
        } else {
            log.error("[RemoteService putVehiclePassRecords] entryExitType error, entryExitType:{}", passStatus);
            return null;
        }

        vehicleHeadImg = Base64ToImageUtil.imageToBase64(pressTextVehicleImgUrl);
        vehicleBodyImg = Base64ToImageUtil.imageToBase64(pressTextHeadImgUrl);
        String pass = passStatus == InOutTypeEnum.IN ? "1" : "2";

        String serialNo = systemConfigService.get(SystemConfig.companyCode)+ gateNo + pass + DateUtil.format(DateUtil.parse(passTime, DatePattern.NORM_DATETIME_PATTERN), "yyMMddHHmmss");

        JSONObject dto = new JSONObject();
        dto.put("serialNo", serialNo);
        dto.put("entranceNo", entranceNo);
        dto.put("gateNo", gateNo);
        dto.put("passStatus", passStatus);
        dto.put("passTime", passTime);
        dto.put("plateNo", vehicleCommonLedgerEntity.getVehicleNumber());
        dto.put("plateColor", commonVehicleEntity.getVehiclePlateColor());
        dto.put("vehicleType", vehicleTransportLedgerEntity.getTransportVehicleType());
        dto.put("vin", commonVehicleEntity.getVin());
        dto.put("registerDate", DateUtil.format(transportVehicleEntity.getRegisterTime(), DatePattern.PURE_DATE_PATTERN));
        dto.put("vehicleBrand", transportVehicleEntity.getVehicleBrand());
        dto.put("vehicleModel", transportVehicleEntity.getVehicleModel());
        dto.put("fuelType", transportVehicleEntity.getFuelType());
        dto.put("emissionStandard", transportVehicleEntity.getEmissionStandard());
        dto.put("connectedStatus", StrUtil.isEmpty(transportVehicleEntity.getNetworkStatus()) ? "0" : transportVehicleEntity.getNetworkStatus());
        dto.put("handMove", handMove);
        dto.put("useCharacter", transportVehicleEntity.getUsageNature());
        dto.put("transportGoodType", "4");
        if (StringUtils.isNoneBlank(transportGoods) && transportVolume != null) {
            dto.put("transportGoods", transportGoods);
            dto.put("transportVolume", transportVolume);
            dto.put("transportGoodsUnit", "kg");
        }

        SM2 sm2 = SmUtil.sm2(null, getPublicKey());
        String value = sm2.encryptHex(dto.toJSONString(), CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey);
        JSONObject entries = new JSONObject();
        entries.put("company_code", systemConfigService.get(SystemConfig.companyCode));
        entries.put("access_token", getAccessToken());
        entries.put("encrypt_method", "2");
        entries.put("value", value);
        entries.put("vehicleHeadImg", vehicleHeadImg);
        entries.put("vehicleBodyImg", vehicleBodyImg);


        return requestHandle(entries, BaseResponse.class, "/api/v1/vehiclePassRecords/putVehiclePassRecords");
    }

    private String pressTextImg(String inputPath, String vehicleNumber, String passTime, String entryExitCode, String roadCode) {

        try {
            final File fileInput = FileUtil.file(inputPath);
            if (null == fileInput || !fileInput.exists()) {
                log.info("[GOV pressTextImg] 上报车辆通行记录P图异常, picMiss 源文件丢失, vehicleNumber:{}, passTime:{}, fileInput:{} not exist", vehicleNumber, passTime, inputPath);
                return inputPath;
            }

            final String outputPath = inputPath.substring(0, inputPath.lastIndexOf(".")) + "-photoshop.jpeg";


            ImgUtil.pressText(//
                    fileInput,
                    FileUtil.file(outputPath),
                    String.format("车牌号:%s  通行时间:%s  出入口编号:%s  道闸编号:%s", vehicleNumber, passTime, entryExitCode, roadCode), Color.WHITE,
                    new Font("宋体", Font.PLAIN, 36), //字体
                    -750, //x坐标修正值。 默认在中间，偏移量相对于中间偏移
                    750, //y坐标修正值。 默认在中间，偏移量相对于中间偏移
                    0.8f//透明度：alpha 必须是范围 [0.0, 1.0] 之内（包含边界值）的一个浮点数字
            );
            return outputPath;
        } catch (Exception e) {
            log.error("[GOV pressTextImg] 上报车辆通行记录P图异常 vehicleNumber:{}, passTime:{} Error", vehicleNumber, passTime, e);
            return inputPath;
        }
    }


    /**
     * 通用 HTTP 请求处理方法
     * <p>
     * 封装与天津监管平台接口交互的通用逻辑。
     * 负责发送 JSON 格式的 POST 请求并将响应结果反序列化为指定类型。
     * </p>
     *
     * @param jsonObject 请求参数 JSON 对象
     * @param clazz 响应结果对应的类类型，用于 JSON 反序列化
     * @param url 请求的接口路径（相对于天津平台主机地址）
     * @param <T> 响应对象类型
     * @return 解析后的响应对象
     * @throws IOException 当网络请求或响应解析异常时抛出
     */
    public <T> T requestHandle(JSONObject jsonObject, Class<T> clazz, String url) throws IOException {
        // 创建 OkHttpClient 客户端实例，用于发起 HTTP 请求
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
        // 构造请求体，将 JSON 参数转为字符串并指定 Content-Type
        RequestBody body = RequestBody.create(jsonObject.toJSONString(), mediaType);

        // 打印请求日志，方便排查接口调用问题
        log.info("[tj远程请求参数 {}] params:{}", url, VehicleStringUtil.toNonBase64JsonString(jsonObject));
        // 构建 POST 请求对象，拼接完整 URL 并附带请求体
        Request request = new Request.Builder()
                .url(systemConfigService.get(SystemConfig.tjDovEndpoint) + url)
                .post(body)
                .build();

        // 执行 HTTP 请求，并使用 try-with-resources 自动关闭连接
        try (Response response = client.newCall(request).execute()){
            // 校验响应是否成功返回，若失败则抛出业务异常
            if (!response.isSuccessful() || response.body() == null) {
                log.info("[tj远程请求异常 {}}] result:{}", url , JSONObject.toJSONString(response));
                throw VehicleAccessException.NET_ERROR.newInstance("请求tj接口异常");
            }

            // 获取响应体字符串内容
            String responseBody = response.body().string();
            // 打印返回结果日志，便于调试与数据追踪
            log.info("[tj远程请求结果 {}}] result:{}", url ,responseBody);
            // 将响应字符串反序列化为指定类型对象并返回
            return JSONObject.parseObject(responseBody, clazz);
        }
    }

    /**
     * 获取 Access Token
     * <p>
     * 优先从缓存中读取 Token，若缓存不存在或过期，则通过远程接口重新获取。
     * 成功获取后会将 Token 写入缓存中，以减少频繁请求。
     * </p>
     *
     * @return 有效的 access_token 字符串
     * @throws VehicleAccessException 当远程请求失败时抛出
     */
    public String getAccessToken() {

        // 尝试从缓存中获取 Access Token
        AccessTokenDto accessTokenDto = accessTokenCache.get();

        // 若缓存中存在有效 Token，则直接返回
        if (accessTokenDto != null) {
            return accessTokenDto.getAccessToken();
        }

        try {
            // 若缓存为空，则重新调用远程接口获取新的 Token
            AccessTokenDto accessToken = getAccessToken(systemConfigService.get(SystemConfig.companyUsername), systemConfigService.get(SystemConfig.companyPassword));
            // 将新获取的 Token 写入缓存，避免重复请求
            accessTokenCache.put(accessToken);
            return accessToken.getAccessToken();
        } catch (IOException e) {
            // 捕获网络异常并转换为系统定义的业务异常
            log.error("[获取token异常] error", e);
            throw VehicleAccessException.NET_ERROR.newInstance("获取token异常");
        }
    }


    /**
     * 获取公钥
     * <p>
     * 优先从缓存中读取公钥信息；若不存在则通过接口重新获取。
     * 通常用于 SM2 加密过程中。
     * </p>
     *
     * @return 平台分配的公钥字符串
     * @throws VehicleAccessException 当请求异常或网络错误时抛出
     */
    public String getPublicKey() {

        // 尝试从缓存中获取公钥信息
        AccessTokenDto accessTokenDto = accessTokenCache.get();

        // 若缓存中存在有效公钥，则直接返回
        if (accessTokenDto != null) {
            return accessTokenDto.getPublicKey();
        }

        try {
            // 若缓存中无公钥，则通过远程接口重新获取 Access Token（同时包含公钥）
            AccessTokenDto accessToken = getAccessToken(systemConfigService.get(SystemConfig.companyUsername), systemConfigService.get(SystemConfig.companyPassword));
            // 缓存新的 Token 与公钥
            accessTokenCache.put(accessToken);
            return accessToken.getPublicKey();
        } catch (IOException e) {
            // 网络请求或解析失败时，记录日志并抛出业务异常
            log.error("[获取publicKey异常] error", e);
            throw VehicleAccessException.NET_ERROR.newInstance("获取publicKey异常");
        }
    }


}
